package endpoint

import (
	"center/db"
	"common/node_pieces"
	"common/pb"
	rs2 "common/rs"
	"common/uuid"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"time"
)

func (s *MetainfoServer) BeginObject(ctx context.Context, req *pb.ObjectBeginRequest) (_ *pb.ObjectBeginResponse, err error) {
	var streamId uuid.UUID
	defer func() {
		err = Error.Wrap(err)
		s.log.Info("BeginObject",
			zap.String("streamId", streamId.String()), zap.ByteString("bucket", req.Bucket), zap.Error(err))
	}()
	//检查参数
	apiKey, err := s.validateAuth(ctx, req.Header)
	if err != nil {
		return nil, err
	}
	//检查bucket是否存在
	bucket, _err := s.db.GetBucketByName(ctx, string(req.Bucket), apiKey.Head())
	if _err != nil {
		if errors.Is(_err, gorm.ErrRecordNotFound) {
			err = fmt.Errorf("bucket[%s]  not found", req.Bucket)
			return nil, err
		} else {
			err = fmt.Errorf("bucket[%s] find error: %s", req.Bucket, err)
			return nil, err
		}
	}
	var inline = false
	var inlineExist = false
	if req.InlineData != nil && len(req.InlineData) > 0 {
		inline = true
	}
	streamId, err = uuid.New()
	if err != nil {
		return nil, err
	}
	if !inline {
		//todo 删除存在的同名文件，node应该要对应删除
		_ = s.db.DeleteObject(ctx, db.ObjectOne{EncryptObjectKey: req.EncryptedObjectKey, BucketId: bucket.ID})
	} else {
		obj, err2 := s.db.GetObject(ctx, db.ObjectOne{EncryptObjectKey: req.EncryptedObjectKey, BucketId: bucket.ID})
		if err2 == nil {
			fmt.Println("object 存在, stream id ", obj.StreamId.String())
			streamId = obj.StreamId
			inlineExist = true
		} else {
			fmt.Println("stream not exist")
		}
	}

	//过期时间
	expireTime := time.Time{}
	if req.ExpireTime > 0 {
		//最低有效期五分钟
		if req.ExpireTime <= time.Now().Unix()+300 {
			return nil, errors.New("过期时间错误")
		}
		expireTime = time.Unix(req.ExpireTime, 0)
	} else if s.config.Expiration > 0 {
		expireTime = time.Now().Add(s.config.Expiration)
	}
	//落库
	param := &db.BeginObjectParam{
		StreamId:         streamId,
		BucketId:         bucket.ID,
		ObjectKey:        string(req.PlainObjectKey),
		EncryptObjectKey: req.EncryptedObjectKey,
		EncryptedKey:     req.EncryptedContentKey,
		ExpireTime:       expireTime,
		Attribute:        req.Attributes,
	}
	if inline {
		err = s.db.InsertOrCreateInlineObject(ctx, param, inlineExist, req.InlineData)
	} else {
		_, err = s.db.BeginObject(ctx, param)
	}

	if err != nil {
		return nil, err
	}
	return &pb.ObjectBeginResponse{
		Bucket:     []byte(bucket.Bucket),
		Version:    0,
		StreamId:   streamId.Bytes(),
		Rs:         rs2.ToPbRs(s.rs),
		ExpireTime: int32(expireTime.Unix()),
	}, nil
}

// BeginSegment 目前已废弃不在使用
func (s *MetainfoServer) BeginSegment(ctx context.Context, req *pb.SegmentBeginRequest) (*pb.SegmentBeginResponse, error) {
	_, err := s.validateAuth(ctx, req.Header)
	if err != nil {
		return nil, Error.Wrap(err)
	}
	nodes, err := s.db.GetUploadNodesByWeight(ctx, &db.NodeSelection{
		Quantity:      int(req.Quantity),
		IncludeNodeId: req.IncludeNodeId,
		MinVersion:    s.config.MinNodeVersion,
	})
	if err != nil {
		s.log.Error("获取节点失败", zap.Int32("Quantity", req.Quantity))
		return nil, Error.New("获取节点失败")
	}
	response := pb.SegmentBeginResponse{}
	total := uint(len(nodes))
	if total == 0 {
		return &response, Error.New("no nodes found")
	}
	//如果节点数量大于等于允许数据折叠的最小节点数量，将会重复补充节点到请求所需的节点数量
	if s.config.AllowFoldNum > 0 && s.config.AllowFoldNum <= total {
		total = uint(req.Quantity)
	}
LOOP:
	for {
		for _, n := range nodes {
			response.NodeInfo = append(response.NodeInfo, &pb.NodeInfo{
				NodeId: n.ID.Bytes(),
				StorageNodeAddress: &pb.NodeAddress{
					Ip:       n.Ip,
					Ipv6:     n.Ipv6,
					DeviceId: n.DevId,
					Port:     int32(n.Port),
					Nat:      int32(n.Nat),
				},
				PeerFwd: int32(n.Fwd),
			})
			total--
			if total == 0 {
				break LOOP
			}
		}
	}

	response.SegmentId = req.StreamId
	return &response, nil
}

func (s *MetainfoServer) CommitSegment(ctx context.Context, req *pb.SegmentCommitRequest) (_ *pb.SegmentCommitResponse, err error) {
	var streamId uuid.UUID
	nodePieces := make(node_pieces.NodePieces)
	defer func() {
		err = Error.Wrap(err)
		s.log.Info("CommitSegment",
			zap.String("streamId", streamId.String()), zap.Int32("position", req.SegmentIndex), zap.String("nodePieces", nodePieces.String()),
			zap.Error(err))
	}()
	_, err = s.validateAuth(ctx, req.Header)
	if err != nil {
		return nil, err
	}
	//检查segment对应的对象
	object, err := s.db.GetObjectByStreamId(ctx, req.StreamId)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			err = errors.New("Object.streamId not found")
			return nil, err
		} else {
			err = fmt.Errorf("Object.streamId find err: %s", err)
			return nil, err
		}
	}
	streamId = object.StreamId
	segment := db.CommitSegmentParam{
		StreamId:   object.StreamId,
		Position:   int(req.SegmentIndex),
		PieceNum:   uint8(len(req.Infos)),
		NodePieces: nodePieces,
		Pad:        int(req.Padding),
		PlainSize:  int64(req.SegmentSize),
	}
	for _, info := range req.Infos {
		nid, errUUID := uuid.FromBytes(info.NodeId)
		if errUUID != nil {
			continue
		}
		segment.NodePieces[uint8(info.Index)] = s.db.NodeIdToIdx(ctx, nid)
	}
	if int(segment.PieceNum) != segment.NodePieces.Size() {
		_reqInfos, _ := json.Marshal(req.Infos)
		err = errors.New("CommitSegment 分片存储节点错误")
		s.log.Error(err.Error(), zap.ByteString("req.infos", _reqInfos),
			zap.String("segment.NodePieces", segment.NodePieces.String()))
		return nil, err
	}
	_, err = s.db.CommitSegment(ctx, &segment)
	if err != nil {
		return nil, err
	}
	return &pb.SegmentCommitResponse{
		SuccessfulPieces: int32(len(req.Infos)),
	}, nil
}

func (s *MetainfoServer) CommitObject(ctx context.Context, req *pb.ObjectCommitRequest) (_ *pb.ObjectCommitResponse, err error) {
	var streamId uuid.UUID
	defer func() {
		err = Error.Wrap(err)
		s.log.Info("CommitObject", zap.String("streamId", streamId.String()), zap.Error(err))
	}()
	_, err = s.validateAuth(ctx, req.Header)
	if err != nil {
		return nil, err
	}
	streamId, err = uuid.FromBytes(req.StreamId)
	if err != nil {
		return nil, err
	}
	err = s.db.CommitObject(ctx, &db.CommitObjectParam{
		StreamId:           streamId,
		EncryptedObjectKey: req.EncryptedObjectKey,
		PlainSize:          int64(req.PlainSize),
		SegmentCount:       int(req.SegmentCount),
		SegmentSize:        int(req.SegmentSize),
		Rs:                 rs2.PbToRs(req.Rs),
	})
	return &pb.ObjectCommitResponse{}, err
}
